03.1 精通自定义 View 之属性动画——ValueAnimator 的基本使用

返回自定义 View 目录

3.1.1 概述

Animation:
    · View Animation(视图动画,API Level 1)
        · Tween Animation(补间动画)
        · Frame Animation(逐帧动画)
    · Property Animation(属性动画,API Level 11)
        · ValueAnimator
        · ObjectAnimator

视图动画仅能对指定的控件做动画,而属性动画是通过改变控件的某一属性值来做动画的。视图动画还有一个缺陷,就是只能点击原始区域才能响应事件。

3.1.2 ValueAnimator 的简单使用

ValueAnimator 不会对控件执行任何操作,我们可以给它设定从哪个值运动到哪个值,通过监听这些值的渐变过程来使自己操作控件。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MainActivity extends AppCompatActivity {
private TextView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
mView = findViewById(R.id.tv);
findViewById(R.id.start_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doAnimation();
}
});
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "你逮到我了", Toast.LENGTH_SHORT).show();
}
});
}
private void doAnimation() {
final int left, top;
left = mView.getLeft();
top = mView.getTop();
ValueAnimator animator = ValueAnimator.ofInt(0, 400, 200);
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (Integer) animation.getAnimatedValue();
mView.layout(curValue + left, curValue + top,
curValue + left + mView.getWidth(),
curValue + top + mView.getHeight());
}
});
animator.start();
}
}

ValueAnimator 只负责对指定值区间进行动画运算,我们需要对运算过程进行监听,然后自己对控件执行动画操作;动画结束后,控件仍然可以响应单击事件。

3.1.3 常用函数

1. ofInt 与 ofFloat

1
2
public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)

参数类型都是可变长参数,如上例中的 ofInt(0, 400, 200) 就表示从数字 0 变化到数字 400 再变化到 200。

1
public Object getAnimatedValue()

上例中可以强转为 Integer 是因为使用的是 ofInt 方法设定动画初始值。当使用 ofFloat 函数设定初始值后,则需要强转为 Float 类型。后面介绍的 ofObject 函数也是如此。

2. 常用函数

方法 描述
ValueAnimator setDuration(long duration) 设置动画时长,单位是毫秒
Object getAnimatedValue() 获取 ValueAnimator 在运动时当前运动点的值
void start() 开始动画
void cancel() 取消动画
void setRepeatCount(int value) 循环次数,ValueAnimator.INFINITE 表示无线循环
void setRepeatMode(int value) 循环模式:ValueAnimator.RESTART、REVERSE
void setStartDelay(long startDelay) 延时多久开始,毫秒
ValueAnimator clone() 完全克隆一个 ValueAnimator 示例,包括所有设置和对监听器代码的处理。

3. 监听器

1)添加监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 监听器一:监听动画过程中值的实时变化
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// doSomething
}
});
// 监听器二:监听动画变化时的 4 个状态
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
});

2)移除监听器

1
2
3
4
5
6
7
// 移除 AnimatorUpdateListener
void removeUpdateListener(AnimatorUpdateListener listener);
void removeAllUpdateListeners();
// 移除 AnimatorListener
void removeListener(AnimatorListener listener);
void removeAllListeners();

3.1.4 示例:弹跳加载中效果

自定义 LoadingImageView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class LoadingImageView extends AppCompatImageView {
private int mTop;
// 当前动画图片索引
private int mCurImgIndex = 0;
// 动画图片总张数
private static int mImgCount = 3;
public LoadingImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
ValueAnimator animator = ValueAnimator.ofInt(0, 200, 0);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setDuration(2000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int dx = (Integer) animation.getAnimatedValue();
setTop(mTop - dx);
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
setImageDrawable(getResources().getDrawable(R.drawable.pic_1));
}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {
mCurImgIndex++;
switch (mCurImgIndex % mImgCount) {
case 0:
setImageDrawable(ResourcesCompat.getDrawable(
getResources(), R.drawable.pic_1, null));
break;
case 1:
setImageDrawable(ResourcesCompat.getDrawable(
getResources(), R.drawable.pic_2, null));
break;
case 2:
setImageDrawable(ResourcesCompat.getDrawable(
getResources(), R.drawable.pic_3, null));
break;
}
}
});
animator.start();
}
@Override
protected void onLayout(boolean changed, int left,int top,int right,int bottom) {
super.onLayout(changed, left, top, right, bottom);
mTop = top;
}
}

act_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<com.xxt.xtest.LoadingImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="100dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/pic_1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="加载中..."/>
</LinearLayout>